To Learn is to get out of your comfort-Zone¶
Dependencies¶
This project uses PyTorch and several related libraries for classification using transfer learning. Below is a breakdown of the main imports used:
Core Libraries¶
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
These are the core PyTorch modules for building and training neural networks.
Data Handling¶
from torchvision import datasets, transforms
from torch.utils.data import Dataset, DataLoader, Subset
from PIL import Image
These are used for loading, preprocessing, and managing image datasets, including custom datasets and built-in transformations.
Model Utilities¶
import torchvision.models as models
from torch_lr_finder import LRFinder
from torch.optim.lr_scheduler import ReduceLROnPlateau
torchvision.models: Provides pretrained models like ResNet, VGG, etc.LRFinder: Helps determine the optimal learning rate.ReduceLROnPlateau: Automatically reduces the learning rate when a metric has stopped improving.
General Utilities¶
import os
from pathlib import Path
import shutil
import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tqdm.notebook import tqdm
These libraries support file and directory operations, numerical processing, plotting, and progress visualization.
import torch
import torch.utils.data
from pathlib import Path
from torchvision import datasets, transforms
from torch.utils.data import Dataset, DataLoader
from torch.utils.data import Subset
import multiprocessing
import os
from PIL import Image
import math
import shutil
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models as models
import timm
#from torch_lr_finder import LRFinder
from torch.autograd import Variable
import torch.optim as optim
from torch.optim.lr_scheduler import ReduceLROnPlateau
import matplotlib.pyplot as plt
from tqdm.notebook import tqdm
import numpy as np
import pandas as pd
import random
SEED = 42
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)
torch.cuda.manual_seed(SEED)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
Data Preprocessing¶
This project uses custom data transformations for training and validation datasets, defined using torchvision.transforms. These transformations prepare the input images for a convolutional neural network.
data_transforms = {
"train": transforms.Compose([
transforms.Resize((512, 512)),
#transforms.RandAugment(num_ops = 3, magnitude=4, interpolation=transforms.InterpolationMode.BILINEAR),
transforms.ToTensor(),
transforms.Normalize([0.4429, 0.5322, 0.3367], [0.1901, 0.1883, 0.2020])
]),
"valid": transforms.Compose([
transforms.Resize((512, 512)),
transforms.ToTensor(),
transforms.Normalize([0.4429, 0.5322, 0.3367], [0.1901, 0.1883, 0.2020])
])
}
Our Entire Data Set Images(training + testing) + Train csv file + Test csv file¶
data = "/kaggle/input/crop-pestdatasets/Combined_pestDataset"
train = pd.read_csv(data + "/Train.csv")
test = pd.read_csv(data + "/Test.csv")
train["Label"].value_counts()
Label 0 810 1 809 Name: count, dtype: int64
Dataset Preparation¶
This section organizes the dataset into a structure suitable for PyTorch's ImageFolder or custom dataset loading. The images are divided into training and test directories.
Directory Structure Created:¶
/kaggle/working/data/
├── train/
│ ├── 0/
│ │ └── image\_001.jpg
│ ├── 1/
│ │ └── image\_002.jpg
│ └── ...
└── test/
├── image\_101.jpg
├── image\_102.jpg
└── ...
source_dir = data + '/Images'
output_dir = '/kaggle/working/data'
train_dir = os.path.join(output_dir, 'train')
test_dir = os.path.join(output_dir, 'test')
# Create train class folders
for label in train['Label'].unique():
os.makedirs(os.path.join(train_dir, str(label)), exist_ok=True)
# Create test folder (no class folders)
os.makedirs(test_dir, exist_ok=True)
# Move train images
for _, row in train.iterrows():
src = os.path.join(source_dir, row['Image_id'])
dst = os.path.join(train_dir, str(row['Label']), row['Image_id'])
if os.path.exists(src):
shutil.copy2(src, dst)
# Move test images
for _, row in test.iterrows():
src = os.path.join(source_dir, row['Image_id'])
dst = os.path.join(test_dir, row['Image_id'])
if os.path.exists(src):
shutil.copy2(src, dst)
Loading the Dataset with ImageFolder¶
The training and validation datasets are loaded using PyTorch’s ImageFolder, which expects data organized in subdirectories named by class labels (as previously created).
train_data = datasets.ImageFolder(
output_dir + "/train",
transform = data_transforms["train"]
)
valid_data = datasets.ImageFolder(
output_dir + "/train",
transform = data_transforms["valid"]
)
Splitting the Dataset: Train/Validation Samplers¶
To create a proper training/validation split from a single dataset, we randomly partition the indices of the dataset and use PyTorch's SubsetRandomSampler to control which samples go into each set.
- Training 80%
- Testing 20%
# obtain training indices that will be used for validation
n_tot = len(train_data)
indices = torch.randperm(n_tot)
limit = -1
valid_size = 0.2
# If requested, limit the number of data points to consider
if limit > 0:
indices = indices[:limit]
n_tot = limit
split = int(math.ceil(valid_size * n_tot))
train_idx, valid_idx = indices[split:], indices[:split]
# define samplers for obtaining training and validation batches
train_sampler = torch.utils.data.SubsetRandomSampler(train_idx)
valid_sampler = torch.utils.data.SubsetRandomSampler(valid_idx)
Initializing Data Loaders Dictionary¶
We initialize a dictionary to store PyTorch DataLoader objects for both training and validation phases.
data_loaders = {"train": None, "valid": None}
# define samplers for obtaining training and validation batches
train_sampler = torch.utils.data.SubsetRandomSampler(train_idx)
valid_sampler = torch.utils.data.SubsetRandomSampler(valid_idx)
# Get the number of CPU cores
#The num_workers parameter in DataLoader controls how many subprocesses are used for data loading.
#Using multiple workers allows the system to load and preprocess data in parallel while the GPU is busy training.
num_workers = multiprocessing.cpu_count()
num_workers
4
Creating Data Loaders¶
We now create DataLoader objects for the training and validation datasets using the batch size and samplers defined earlier.
batch_size = 32 #Number of samples processed in each iteration
# prepare data loaders
data_loaders["train"] = torch.utils.data.DataLoader(
train_data,
batch_size=batch_size,
sampler=train_sampler,
num_workers=num_workers,
)
data_loaders["valid"] = torch.utils.data.DataLoader(
valid_data,
batch_size=batch_size,
sampler=valid_sampler,
num_workers=num_workers,
)
Visualizing images without transforms¶
def visualize_one_batch(data_loaders, max_n=6, unnormalize=True):
"""
Visualizes a batch of images from the 'valid' DataLoader, optionally reversing transforms.
Args:
- data_loaders (dict): Dictionary with DataLoader objects (expects 'valid' key).
- max_n (int): Number of images to show.
- unnormalize (bool): Whether to undo normalization.
"""
# One batch from validation set
dataiter = iter(data_loaders['valid'])
images, labels = next(dataiter)
# Inverse normalization for your custom mean/std
if unnormalize:
inv_normalize = transforms.Normalize(
mean=[-m/s for m, s in zip([0.4429, 0.5322, 0.3367], [0.1901, 0.1883, 0.2020])],
std=[1/s for s in [0.1901, 0.1883, 0.2020]]
)
images = torch.stack([inv_normalize(img) for img in images])
# Plot
fig = plt.figure(figsize=(12, 6))
for idx in range(min(max_n, len(images))):
ax = fig.add_subplot(2, max_n // 2, idx + 1, xticks=[], yticks=[])
npimg = images[idx].numpy()
npimg = np.transpose(npimg, (1, 2, 0))
npimg = np.clip(npimg, 0, 1) # Clamp to valid range for display
ax.imshow(npimg)
ax.set_title(f'Label: {labels[idx].item()}')
plt.tight_layout()
plt.show()
#plotting ...
visualize_one_batch(data_loaders)
Visualizing images with transforms¶
def visualize_one_batch(data_loaders, max_n=6):
"""
Visualizes a batch of images from the 'train' DataLoader.
Args:
- data_loaders (dict): Dictionary containing DataLoader objects for different datasets.
- max_n (int): Maximum number of images to visualize. Default is 6.
"""
# Check if 'train' DataLoader exists in data_loaders
if 'train' not in data_loaders:
raise ValueError("DataLoader for 'train' dataset not found in data_loaders dictionary.")
# Obtain one batch of training images
dataiter = iter(data_loaders['valid'])
images, labels = next(dataiter)
# Plot images
fig = plt.figure(figsize=(10, 10))
for idx in range(max_n):
ax = fig.add_subplot(2, max_n // 2, idx + 1, xticks=[], yticks=[])
ax.imshow(np.transpose(images[idx], (1, 2, 0)))
ax.set_title(f'Label: {labels[idx].item()}')
plt.show()
#usage
visualize_one_batch(data_loaders)
# Define the device (GPU if available, otherwise CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class TransferNet(nn.Module):
def __init__(self, n_classes=2):
super(TransferNet, self).__init__()
resnet = models.resnet18(pretrained=True)
# Freeze all parameters initially
for param in resnet.parameters():
param.requires_grad = False
# Unfreeze last two layers (layer3 and layer4)
for name, param in resnet.named_parameters():
if "layer3" in name or "layer4" in name:
param.requires_grad = True
# Remove the final fully connected layer
self.resnet = nn.Sequential(*list(resnet.children())[:-1])
# Define your classifier
num_ftrs = resnet.fc.in_features
self.classifier = nn.Sequential(
nn.Linear(num_ftrs, 500),
nn.BatchNorm1d(500),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(500, 120),
nn.BatchNorm1d(120),
nn.ReLU(),
nn.Dropout(p=0.2),
nn.Linear(120, n_classes)
)
def forward(self, x):
x = self.resnet(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
# Define your model and send it to GPU if available
model = TransferNet(n_classes=2).to(device)
/usr/local/lib/python3.11/dist-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead. warnings.warn( /usr/local/lib/python3.11/dist-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=ResNet18_Weights.IMAGENET1K_V1`. You can also use `weights=ResNet18_Weights.DEFAULT` to get the most up-to-date weights. warnings.warn(msg) Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth 100%|██████████| 44.7M/44.7M [00:00<00:00, 203MB/s]
# Define loss function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=2.95e-03)
# Scheduler setup
from torch.optim.lr_scheduler import OneCycleLR
num_epochs = 30
#scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs) # T_max = number of epochs
scheduler = OneCycleLR(optimizer, max_lr=0.01, steps_per_epoch=len(data_loaders["train"]), epochs=num_epochs)
#scheduler = ReduceLROnPlateau(optimizer, mode="min", verbose=True, threshold=0.01)
# Function to plot training curves
def plot_training_curves(train_losses, val_losses, val_accuracies, learning_rates):
plt.figure(figsize=(15, 5))
# Plot Losses
plt.subplot(1, 3, 1)
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Val Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training and Validation Losses')
plt.legend()
# Plot Accuracy
plt.subplot(1, 3, 2)
plt.plot(val_accuracies, label='Val Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Validation Accuracy')
plt.legend()
# Plot Learning Rate
plt.subplot(1, 3, 3)
plt.plot(learning_rates, marker='o')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('Learning Rate Schedule')
plt.grid(True)
plt.tight_layout()
plt.show()
# Training loop with model saving based on best accuracy and best loss
def train_model(model, criterion, optimizer, scheduler, num_epochs=10):
best_val_acc = 0.0
best_val_loss = float('inf')
best_acc_model_weights = None
best_loss_model_weights = None
train_losses = []
val_losses = []
val_accuracies = []
learning_rates = []
for epoch in range(num_epochs):
# Training phase
model.train()
running_loss = 0.0
tqdm_train = tqdm(data_loaders["train"], desc=f"Epoch {epoch+1}/{num_epochs}", ncols=100)
for inputs, labels in tqdm_train:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
#loss = criterion(outputs, labels.float().unsqueeze(1))
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
tqdm_train.set_postfix({'Training Loss': running_loss / len(data_loaders["train"].dataset)})
epoch_train_loss = running_loss / len(data_loaders["train"].dataset)
train_losses.append(epoch_train_loss)
# Validation phase
model.eval()
val_loss = 0.0
correct = 0
total = 0
tqdm_val = tqdm(data_loaders["valid"], desc="Validating", ncols=100)
with torch.no_grad():
for inputs, labels in tqdm_val:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
loss = criterion(outputs, labels)
#loss = criterion(outputs, labels.float().unsqueeze(1))
val_loss += loss.item() * inputs.size(0)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
tqdm_val.set_postfix({'Validation Loss': val_loss / len(data_loaders["valid"].dataset)})
epoch_val_loss = val_loss / len(data_loaders["valid"].dataset)
val_losses.append(epoch_val_loss)
val_acc = correct / total
val_accuracies.append(val_acc)
print(f"Epoch [{epoch + 1}/{num_epochs}], "
f"Train Loss: {epoch_train_loss:.4f}, "
f"Val Loss: {epoch_val_loss:.4f}, "
f"Val Acc: {val_acc:.2%}")
# Adjust learning rate based on validation loss
scheduler.step(epoch_val_loss)
learning_rates.append(optimizer.param_groups[0]['lr'])
# Save the best models based on validation accuracy and validation loss
if val_acc > best_val_acc:
best_val_acc = val_acc
best_acc_model_weights = model.state_dict()
if epoch_val_loss < best_val_loss:
best_val_loss = epoch_val_loss
best_loss_model_weights = model.state_dict()
# Plot training curves after each epoch
plot_training_curves(train_losses, val_losses, val_accuracies, learning_rates)
# Load the best models based on validation accuracy and validation loss
if best_acc_model_weights:
model.load_state_dict(best_acc_model_weights)
best_model_acc = model
if best_loss_model_weights:
model.load_state_dict(best_loss_model_weights)
best_model_loss = model
# Return best model and best accuracy
return best_model_acc, best_val_acc
"""from sklearn.metrics import roc_auc_score
# Modified function to plot training curves with AUC
def plot_training_curves(train_losses, val_losses, val_aucs, learning_rates):
plt.figure(figsize=(15, 5))
# Plot Losses
plt.subplot(1, 3, 1)
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Val Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training and Validation Losses')
plt.legend()
# Plot AUC
plt.subplot(1, 3, 2)
plt.plot(val_aucs, label='Val AUC')
plt.xlabel('Epoch')
plt.ylabel('AUC')
plt.title('Validation AUC')
plt.legend()
# Plot Learning Rate
plt.subplot(1, 3, 3)
plt.plot(learning_rates, marker='o')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('Learning Rate Schedule')
plt.grid(True)
plt.tight_layout()
plt.show()"""
"from sklearn.metrics import roc_auc_score\n\n# Modified function to plot training curves with AUC\ndef plot_training_curves(train_losses, val_losses, val_aucs, learning_rates):\n plt.figure(figsize=(15, 5))\n\n # Plot Losses\n plt.subplot(1, 3, 1)\n plt.plot(train_losses, label='Train Loss')\n plt.plot(val_losses, label='Val Loss')\n plt.xlabel('Epoch')\n plt.ylabel('Loss')\n plt.title('Training and Validation Losses')\n plt.legend()\n\n # Plot AUC\n plt.subplot(1, 3, 2)\n plt.plot(val_aucs, label='Val AUC')\n plt.xlabel('Epoch')\n plt.ylabel('AUC')\n plt.title('Validation AUC')\n plt.legend()\n\n # Plot Learning Rate\n plt.subplot(1, 3, 3)\n plt.plot(learning_rates, marker='o')\n plt.xlabel('Epoch')\n plt.ylabel('Learning Rate')\n plt.title('Learning Rate Schedule')\n plt.grid(True)\n\n plt.tight_layout()\n plt.show()"
"""# Modified training loop with AUC calculation
def train_model(model, criterion, optimizer, scheduler, num_epochs=10):
best_val_auc = 0.0
best_val_loss = float('inf')
best_auc_model_weights = None
best_loss_model_weights = None
train_losses = []
val_losses = []
val_aucs = []
learning_rates = []
for epoch in range(num_epochs):
# Training phase (unchanged)
model.train()
running_loss = 0.0
tqdm_train = tqdm(data_loaders["train"], desc=f"Epoch {epoch+1}/{num_epochs}", ncols=100)
for inputs, labels in tqdm_train:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
tqdm_train.set_postfix({'Training Loss': running_loss / len(data_loaders["train"].dataset)})
epoch_train_loss = running_loss / len(data_loaders["train"].dataset)
train_losses.append(epoch_train_loss)
# Validation phase (modified for AUC)
model.eval()
val_loss = 0.0
all_probs = []
all_labels = []
tqdm_val = tqdm(data_loaders["valid"], desc="Validating", ncols=100)
with torch.no_grad():
for inputs, labels in tqdm_val:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
loss = criterion(outputs, labels)
val_loss += loss.item() * inputs.size(0)
# Get probabilities and store them
probs = torch.softmax(outputs, dim=1)
all_probs.append(probs.cpu())
all_labels.append(labels.cpu())
tqdm_val.set_postfix({'Validation Loss': val_loss / len(data_loaders["valid"].dataset)})
# Calculate AUC
all_probs = torch.cat(all_probs, dim=0).numpy()
all_labels = torch.cat(all_labels, dim=0).numpy()
val_auc = roc_auc_score(all_labels, all_probs[:, 1])
epoch_val_loss = val_loss / len(data_loaders["valid"].dataset)
val_losses.append(epoch_val_loss)
val_aucs.append(val_auc)
print(f"Epoch [{epoch + 1}/{num_epochs}], "
f"Train Loss: {epoch_train_loss:.4f}, "
f"Val Loss: {epoch_val_loss:.4f}, "
f"Val AUC: {val_auc:.4f}")
# Adjust learning rate based on validation loss (unchanged)
scheduler.step(epoch_val_loss)
learning_rates.append(optimizer.param_groups[0]['lr'])
# Save best models based on AUC and loss
if val_auc > best_val_auc:
best_val_auc = val_auc
best_auc_model_weights = model.state_dict()
if epoch_val_loss < best_val_loss:
best_val_loss = epoch_val_loss
best_loss_model_weights = model.state_dict()
# Plot curves with AUC
plot_training_curves(train_losses, val_losses, val_aucs, learning_rates)
# Load best models
model.load_state_dict(best_auc_model_weights)
return model, best_val_auc"""
'# Modified training loop with AUC calculation\ndef train_model(model, criterion, optimizer, scheduler, num_epochs=10):\n best_val_auc = 0.0\n best_val_loss = float(\'inf\')\n best_auc_model_weights = None\n best_loss_model_weights = None\n\n train_losses = []\n val_losses = []\n val_aucs = []\n learning_rates = []\n\n for epoch in range(num_epochs):\n # Training phase (unchanged)\n model.train()\n running_loss = 0.0\n tqdm_train = tqdm(data_loaders["train"], desc=f"Epoch {epoch+1}/{num_epochs}", ncols=100)\n for inputs, labels in tqdm_train:\n inputs, labels = inputs.to(device), labels.to(device)\n optimizer.zero_grad()\n outputs = model(inputs)\n loss = criterion(outputs, labels)\n loss.backward()\n optimizer.step()\n running_loss += loss.item() * inputs.size(0)\n tqdm_train.set_postfix({\'Training Loss\': running_loss / len(data_loaders["train"].dataset)})\n\n epoch_train_loss = running_loss / len(data_loaders["train"].dataset)\n train_losses.append(epoch_train_loss)\n\n # Validation phase (modified for AUC)\n model.eval()\n val_loss = 0.0\n all_probs = []\n all_labels = []\n\n tqdm_val = tqdm(data_loaders["valid"], desc="Validating", ncols=100)\n with torch.no_grad():\n for inputs, labels in tqdm_val:\n inputs, labels = inputs.to(device), labels.to(device)\n outputs = model(inputs)\n loss = criterion(outputs, labels)\n val_loss += loss.item() * inputs.size(0)\n \n # Get probabilities and store them\n probs = torch.softmax(outputs, dim=1)\n all_probs.append(probs.cpu())\n all_labels.append(labels.cpu())\n \n tqdm_val.set_postfix({\'Validation Loss\': val_loss / len(data_loaders["valid"].dataset)})\n\n # Calculate AUC\n all_probs = torch.cat(all_probs, dim=0).numpy()\n all_labels = torch.cat(all_labels, dim=0).numpy()\n val_auc = roc_auc_score(all_labels, all_probs[:, 1])\n\n epoch_val_loss = val_loss / len(data_loaders["valid"].dataset)\n val_losses.append(epoch_val_loss)\n val_aucs.append(val_auc)\n\n print(f"Epoch [{epoch + 1}/{num_epochs}], "\n f"Train Loss: {epoch_train_loss:.4f}, "\n f"Val Loss: {epoch_val_loss:.4f}, "\n f"Val AUC: {val_auc:.4f}")\n\n # Adjust learning rate based on validation loss (unchanged)\n scheduler.step(epoch_val_loss)\n learning_rates.append(optimizer.param_groups[0][\'lr\'])\n\n # Save best models based on AUC and loss\n if val_auc > best_val_auc:\n best_val_auc = val_auc\n best_auc_model_weights = model.state_dict()\n\n if epoch_val_loss < best_val_loss:\n best_val_loss = epoch_val_loss\n best_loss_model_weights = model.state_dict()\n\n # Plot curves with AUC\n plot_training_curves(train_losses, val_losses, val_aucs, learning_rates)\n\n # Load best models\n model.load_state_dict(best_auc_model_weights)\n return model, best_val_auc'
# Example usage:
best_model, best_val_acc = train_model(model, criterion, optimizer, scheduler, num_epochs=num_epochs)
# Optionally, save the best model for future use
torch.save(best_model.state_dict(), 'best_model.pth')
Epoch 1/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [1/30], Train Loss: 0.1219, Val Loss: 0.0091, Val Acc: 99.07%
/usr/local/lib/python3.11/dist-packages/torch/optim/lr_scheduler.py:240: UserWarning: The epoch parameter in `scheduler.step()` was not necessary and is being deprecated where possible. Please use `scheduler.step()` to step the scheduler. During the deprecation, if epoch is different from None, the closed form is used instead of the new chainable form, where available. Please open an issue if you are unable to replicate your use case: https://github.com/pytorch/pytorch/issues/new/choose. warnings.warn(EPOCH_DEPRECATION_WARNING, UserWarning)
Epoch 2/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [2/30], Train Loss: 0.0267, Val Loss: 0.0137, Val Acc: 98.15%
Epoch 3/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [3/30], Train Loss: 0.0137, Val Loss: 0.0375, Val Acc: 92.90%
Epoch 4/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [4/30], Train Loss: 0.0196, Val Loss: 0.0064, Val Acc: 99.07%
Epoch 5/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [5/30], Train Loss: 0.0130, Val Loss: 0.0063, Val Acc: 99.07%
Epoch 6/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [6/30], Train Loss: 0.0175, Val Loss: 0.0385, Val Acc: 92.28%
Epoch 7/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [7/30], Train Loss: 0.0282, Val Loss: 0.0051, Val Acc: 99.07%
Epoch 8/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [8/30], Train Loss: 0.0066, Val Loss: 0.0022, Val Acc: 99.69%
Epoch 9/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [9/30], Train Loss: 0.0042, Val Loss: 0.0027, Val Acc: 99.69%
Epoch 10/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [10/30], Train Loss: 0.0039, Val Loss: 0.0017, Val Acc: 99.38%
Epoch 11/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [11/30], Train Loss: 0.0023, Val Loss: 0.0033, Val Acc: 99.07%
Epoch 12/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [12/30], Train Loss: 0.0030, Val Loss: 0.0089, Val Acc: 98.46%
Epoch 13/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [13/30], Train Loss: 0.0129, Val Loss: 0.0280, Val Acc: 96.91%
Epoch 14/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [14/30], Train Loss: 0.0111, Val Loss: 0.0101, Val Acc: 98.77%
Epoch 15/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [15/30], Train Loss: 0.0092, Val Loss: 0.0021, Val Acc: 99.69%
Epoch 16/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [16/30], Train Loss: 0.0036, Val Loss: 0.0053, Val Acc: 99.38%
Epoch 17/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [17/30], Train Loss: 0.0013, Val Loss: 0.0057, Val Acc: 98.77%
Epoch 18/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [18/30], Train Loss: 0.0017, Val Loss: 0.0027, Val Acc: 99.38%
Epoch 19/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [19/30], Train Loss: 0.0007, Val Loss: 0.0035, Val Acc: 99.38%
Epoch 20/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [20/30], Train Loss: 0.0012, Val Loss: 0.0039, Val Acc: 99.38%
Epoch 21/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [21/30], Train Loss: 0.0007, Val Loss: 0.0004, Val Acc: 100.00%
Epoch 22/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [22/30], Train Loss: 0.0009, Val Loss: 0.0006, Val Acc: 100.00%
Epoch 23/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [23/30], Train Loss: 0.0005, Val Loss: 0.0006, Val Acc: 100.00%
Epoch 24/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [24/30], Train Loss: 0.0009, Val Loss: 0.0018, Val Acc: 99.69%
Epoch 25/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [25/30], Train Loss: 0.0035, Val Loss: 0.0132, Val Acc: 98.46%
Epoch 26/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [26/30], Train Loss: 0.0023, Val Loss: 0.0046, Val Acc: 98.77%
Epoch 27/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [27/30], Train Loss: 0.0085, Val Loss: 0.0126, Val Acc: 98.46%
Epoch 28/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [28/30], Train Loss: 0.0025, Val Loss: 0.0117, Val Acc: 98.46%
Epoch 29/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [29/30], Train Loss: 0.0067, Val Loss: 0.0007, Val Acc: 99.69%
Epoch 30/30: 0%| | 0/41 [00:00<?, ?it/s]
Validating: 0%| | 0/11 [00:00<?, ?it/s]
Epoch [30/30], Train Loss: 0.0039, Val Loss: 0.0103, Val Acc: 98.46%
Running Inference on Test Images¶
This step loads the trained model and performs inference on all test images, storing only the predicted class labels.
best_val_acc
1.0
Almost perfect😅¶
test_dir = output_dir + '/test'
model = best_model
# Define transformations (same as training since we don't have augumentations)
transform = transforms.Compose([
transforms.Resize((512, 512)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.4429, 0.5322, 0.3367], std=[0.1901, 0.1883, 0.2020])
])
# Prepare model
model = model.to(device)
model.eval()
# Storage for results
results_class = []
results_prob = []
# Loop over all test images
for img_name in os.listdir(test_dir):
if img_name.endswith(('.jpg', '.jpeg', '.png')):
img_path = os.path.join(test_dir, img_name)
img = Image.open(img_path).convert('RGB')
img = transform(img).unsqueeze(0).to(device)
with torch.no_grad():
outputs = model(img)
_, predicted = torch.max(outputs.data, 1)
prob_class1 = torch.softmax(outputs, dim=1)[:, 1].item()
# Store results
results_class.append([img_name, predicted.item()])
results_prob.append([img_name, prob_class1])
# Convert to DataFrame
results_class_df = pd.DataFrame(results_class, columns=['Image_id', 'label'])
results_prob_df = pd.DataFrame(results_prob, columns=['Image_id', 'prob_class1'])
# Save both predictions separately
results_class_df.to_csv('predicted_classes.csv', index=False)
results_prob_df.to_csv('predictedREZ_probabilities_NoTransforms.csv', index=False)
results_class_df.head()
| Image_id | label | |
|---|---|---|
| 0 | id_51lonq2vfen6h.jpg | 0 |
| 1 | id_51i5bkwsy04y1.jpg | 1 |
| 2 | id_v7unhim7vonjk.jpg | 1 |
| 3 | id_ut9h7f66f41l8.jpg | 0 |
| 4 | id_aysfeippqwr7s.jpg | 0 |
results_class_df["label"].value_counts()
label 0 559 1 521 Name: count, dtype: int64
I used the probabilities